#!/usr/bin/python3
# -*- coding: utf-8 -*-

from xml import Selectors

class AnyType:
    'Base type of all CSS classes'
    def __repr__(self):
        return '<CSS node at {}>'.format(hex(id(self)))

    def filter(self, func=(lambda x: True), selftoo=False, walk=False):
        if selftoo and func(self):
            yield self
        for node in self:
            try:
                if func(node):
                    yield node
                if walk:
                    for kid in node.filter(func, False, True):
                        yield kid
            except AttributeError:
                continue

class Stylesheet(list):
    '''CSS DOM parser.
Stylesheet(]source]) -> Stylesheet object # Creates a stylesheet from the source
Stylesheet.feed(source) # Replaces the current DOM tree with a new one \
created from source.'''
    
    def __init__(self, source='', stream=False):
        '''CSS DOM parser.
Stylesheet(]source]) -> Stylesheet object # Creates a stylesheet from the source
Stylesheet.feed(source) # Replaces the current DOM tree with a new one \
created from source.'''
        super(Stylesheet, self).__init__(self)
        self.step = 0 # Current step of execution
        self.curdata = '' # Current collected data
        # List of all of the current node's ancestors
        # self is the root node. Since CSS is based on a block
        # model, the name depth is more adequate than the name
        # ancestors here.
        self.depth = [self]
        # The last executed step.
        # In some steps, there is back and forth between two steps.
        # For example, in comemnt parsing, at the end, the parser must go
        # back to property, Selectors or @-rule parsing,
        # and this variable tells where it was before parsing the comment.
        # It should be set only when needed.
        self.laststep = 0
        # streamed tells the parser whether the content will be chunked or not.
        self.stream = stream
        # Build the tree
        self.feed(source)
        
    def __str__(self):
        # Every node is responsible for its own textual rendering.
        return '\n'.join(str(i) for i in self)
    
    def filter(self, func=(lambda x: True), selftoo=False, walk=False):
        if selftoo and func(self):
            yield self
        for node in self:
            try:
                if func(node):
                    yield node
                if walk:
                    for kid in node.filter(func, False, True):
                        yield kid
            except AttributeError:
                continue
    
    def feed(self, source, last=False):
        # Clear the tree to avoid structure problems.
        if self and not self.stream:
            for i in range(len(self)):
                del self[i]
        # An enumerator is used here because the parser needs to know when
        # the current character is the last one, to end parsing
        # accordingly
        for index, char in enumerate(source):
            # Warning : This parser does not yet support string parsing.
            #           This means that all characters will still be
            #           interpreted as instructions, even if they are in a
            #           string. The string support will come with the Selectors
            #           parsing and the @-rule parsing.
            if self.step == 0:
                # Global parsing, no matter the depth
                # Can pick up comments, properties, at-rules and
                # Selectorss.
                if char == '*' and self.curdata.endswith('/'):
                    # Start of a comment
                    # Trim the Start-Of-Comment character from  curdata
                    # and change curdata into a list, to split the data
                    # that will have to be ignored (the comment), and
                    # the good stuff.
                    self.curdata = [self.curdata[:-1], '']
                    self.laststep = self.step
                    self.step = 2 # Comment parsing
                elif char == '@':
                    # Beginning of an @-rule
                    self.curdata = ''
                    self.step = 1 # @-rule parsing
                elif char == '{':
                    # Start of a block
                    # The current data must be the block's Selectors.
                    # Append the Selectors to depth
                    self.depth.append(Selectors(self.curdata))
                    self.curdata = ''
                elif char == ';':
                    # End of a property
                    # Store the property in the open Selectors
                    # Warning : This does not work correctly when semi-colons
                    #           are present in strings, as for attribute value
                    #           Selectorss. This should be fixed when the
                    #           support for complete Selectors parsing will be
                    #           implemented.
                    property_name, value = self.curdata.split(':', 1)
                    self.depth[-1].append(Property(property_name,
                                                 value))
                    self.curdata = ''
                elif char == '}':
                    # End of a block.
                    # Store it in its parent.
                    toappend = self.depth.pop(-1)
                    self.depth[-1].append(toappend)
                    self.curdata = ''
                    # There is no need to change steps since exiting a block
                    # brings back to global parsing.
                else:
                    # Otherwise, store data, it's gonna be a Selectors.
                    self.curdata += char
            elif self.step == 1:
                # @-rule parsing
                if char == '*' and self.curdata.endswith('/'):
                    # Start of a comment
                    # Trim the Start-Of-Comment character from  curdata
                    # and change curdata into a list, to split the data
                    # that will have to be ignored (the comment), and
                    # the good stuff.
                    self.curdata = [self.curdata[:-1], '']
                    self.laststep = self.step
                    self.step = 2 # Comment parsing
                elif char == ';':
                    # The @-rule was an single instruction.
                    # Warning : This does not work correctly when semi-colons
                    #           are present in strings, as for URIs.
                    #           This should be fixed when the
                    #           support for complete @-rule parsing will be
                    #           implemented.
                    # Append it to its parent.
                    # Warning : This does no parsing whatsoever on the @-rule.
                    #           Complete support for @-rule parsing should be
                    #           added.
                    self.depth[-1].append(AtRule(self.curdata, ''))
                    self.curdata = ''
                    self.step = 0 # Go back to global parsing.
                elif char == '{':
                    # The @-rule starts a block.
                    # Append it to its parent.
                    # Warning : This does no parsing whatsoever on the @-rule.
                    #           Complete support for @-rule parsing should be
                    #           added.
                    self.depth.append(AtRule(self.curdata, ''))
                    self.curdata = ''
                    # Global parsing, since the block will contain Selectorss,
                    # properties, or other @-rules.
                    self.step = 0
                else:
                    self.curdata += char
            elif self.step == 2:
                # If we are in a comment.
                if char == '/' and self.curdata[1].endswith('*'):
                    # The comment is finished.
                    # Append it to the tree.
                    self.depth[-1].append(Comment(self.curdata[1][:-1]))
                    self.step = self.laststep # Go back to the previous step
                    self.curdata = self.curdata[0] # Restore curdata
                else:
                    self.curdata[1] += char
        if last:
            self.step = 0 # Current step of execution
            self.curdata = '' # Current collected data
            # List of all of the current node's ancestors
            # self is the root node. Since CSS is based on a block
            # model, the name depth is more adequate than the name
            # ancestors here.
            self.depth = [self]
            # The last executed step.
            # In some steps, there is back and forth between two steps.
            # For example, in comemnt parsing, at the end, the parser must go
            # back to property, Selectors or @-rule parsing,
            # and this variable tells where it was before parsing the comment.
            # It should be set only when needed.
            self.laststep = 0
            # streamed tells the parser whether the content will be chunked or
            # not.
            self.stream = False

class AtRule(Stylesheet, AnyType):
    'CSS @-rule object.'
    
    def __init__(self, name, args=''):
        'CSS @-rule object.'
        super(AtRule, self).__init__()
        self.name = name
        self.arguments = args
        
    def __str__(self):
        output = '@' + ' '.join([self.name, self.arguments])
        if len(self):
            content = '\n'.join(str(i) for i in self)
            # Make sure that the content is properly indented.
            output += ' {{\n{}\n}}'.format(content.replace('\n', '\n    '))
        else:
            output += ';'
        return output

class Property(AnyType):
    'CSS property object'
    
    def __init__(self, name, value):
        'CSS property object.'
        self.name = name.strip()
        self.value = value.strip()
    
    def __str__(self):
        output = '{}: {};'.format(self.name, self.value)
        return output

class Comment(str, AnyType):
    'CSS comment object.'
    
    def __str__(self):
        return '/*' + self + '*/'

if __name__ == '__main__':
    source = '''
@encoding(utf-8);
@media screen {
    @page {
    test: hi;
        /* This is a useless
        comment
    please understand us.*/
    te/*a*/s/*er*/t: hello;
    }
    te/*a*/s/*er*/t {
        test: hi;
        font: Verdana 12pt bold;
        color: black;
    }
}
'''
    output = Stylesheet(source[:20], stream=True)
    output.feed(source[20:30])
    output.feed(source[30:50])
    output.feed(source[50:], last=True)
    print(output)
